package flash.preloader
{
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import flash.utils.getDefinitionByName;
	
	/**
	 * AbstractPreloader class is the base for creating
	 * custom Flex style preloaders for AS3 Projects in
	 * Flash Builder.
	 * 
	 * @author Omar Gonzalez :: omar@almerblank.com
	 * 
	 */
	public class AbstractPreloader extends MovieClip
	{
		/**
		 * Reference to the Main application Class.
		 */		
		protected var mainClass:String;
		/**
		 * The minimum amount of time to display the preloader.
		 */		
		protected var minimumDisplayTime:Number = 2000;
		/**
		 * The timestamp when preloading started.
		 */
		protected var preloadingStartedAt:Date;
		
		/**
		 * @Constructor
		 * 
		 * The AbstractPreloader sub-class must set the variable mainQualifiedClassName
		 * before calling super();. Without the String name of the main application class
		 * the application can not be started. 
		 */		
		public function AbstractPreloader()
		{
			super();	
			stop();
			addListeners();
			setStageProperties();
		}
		/**
		 * Sets the stage align property to StageAlign.TOP_LEFT and the
		 * stage.scaleMode property to StageScaleMode.NO_SCALE. Override
		 * this method to set custom stage alignment and scaleMode.
		 */		
		protected function setStageProperties():void
		{
			stage.align			= StageAlign.TOP_LEFT;
			stage.scaleMode		= StageScaleMode.NO_SCALE;
		}
		/**
		 * Adds all event listeners for this for the preloader
		 * progress events. 
		 * 
		 */		
		protected function addListeners():void
		{
			root.loaderInfo.addEventListener( Event.INIT, onInitLoading, false, 0, true );
			root.loaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgress, false, 0, true );
			root.loaderInfo.addEventListener( Event.COMPLETE, onComplete, false, 0, true );
			root.loaderInfo.addEventListener( IOErrorEvent.IO_ERROR, onIOError, false, 0, true );
		}
		/**
		 * Removes all event handler methods for the AbstractPreloader object. 
		 * 
		 */		
		protected function removeListeners():void
		{
			root.loaderInfo.removeEventListener( Event.INIT, onInitLoading );
			root.loaderInfo.removeEventListener( ProgressEvent.PROGRESS, onProgress );
			root.loaderInfo.removeEventListener( Event.COMPLETE, onComplete );
			root.loaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, onIOError );
		}
		/**
		 * Instantiates the application class and adds it to the stage. 
		 * 
		 */		
		protected function startApplicationClass():void
		{
			var ApplicationClass:Class = getDefinitionByName( mainClass ) as Class;
			
			if (ApplicationClass)
			{
				removePreloaderChildren();
				
				var application:DisplayObject = new ApplicationClass(stage,loaderInfo) as DisplayObject;
				
				addChild( application as DisplayObject );
			}
		}
		/**
		 * Removes all children in the AbstractPreloader sub-class,
		 * called before creating the instance of the main application
		 * and adding it to the stage.
		 * 
		 */
		protected function removePreloaderChildren():void
		{
			while (numChildren)
			{
				removeChildAt( 0 );
			}
		}
		/**
		 * Handles the Event.INIT event of the preloading process.  Override
		 * this method to set any application variables that might be required
		 * before finishing or during preloading process.
		 * 
		 * @param event Event
		 * 
		 */		
		protected function onInitLoading( event:Event ):void
		{
			preloadingStartedAt = new Date();
			
			root.loaderInfo.removeEventListener( Event.INIT, onInitLoading );
		}
		/**
		 * Handles ProgressEvent objects while the preloading happens.
		 * Override this method in the AbstractPreloader sub-class
		 * to update the progress in the custom preloader.
		 * 
		 *  
		 * @param event ProgressEvent
		 * 
		 */		
		protected function onProgress( event:ProgressEvent ):void
		{
			// Override this method in the AbstractPreloader sub-class
			// to update the progress in the custom preloader.
		}
		/**
		 * Handles the COMPLETE method of for the preloading.  Advances
		 * the SWF to the next frame and starts the application.
		 * 
		 * @param event Event
		 * 
		 */		
		protected function onComplete( event:Event ):void
		{
			var completedTime:Date = new Date();
			var timeElapsed:Number = completedTime.time - preloadingStartedAt.time;
			
			var secondsElapsed:Number = timeElapsed / 1000;
			if (secondsElapsed > minimumDisplayTime)
			{
				_advancePreloading();
			}
			else
			{
				var timeLeft:Number = minimumDisplayTime - secondsElapsed;
				var timer:Timer = new Timer( timeLeft, 1 );
					timer.addEventListener( TimerEvent.TIMER, handleProgressDisplayTimeLeft, false, 0, true );
					timer.start();
			}
		}
		/**
		 * Timer handler if the preloading finishes before the set minimum preloader display time.
		 * 
		 * @param event
		 * 
		 */
		private function handleProgressDisplayTimeLeft(event:TimerEvent):void
		{
			_advancePreloading();
		}
		/**
		 * Advances the preloader to the main application.
		 * 
		 */
		private function _advancePreloading():void
		{
			nextFrame();
		
			removeListeners();
		
			startApplicationClass();
		}
		/**
		 * Handles IOErrorEvents during startup, if this were ever to occur.  
		 * Override in the AbstractPreloader sub-class to handle this error.
		 *  
		 * @param event IOErrorEvent
		 * 
		 */		
		protected function onIOError( event:IOErrorEvent ):void
		{
			// Override this method in the AbstractPreloader sub-class
			// if this error were ever to happen.
		}
	}
}